﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MishraReader.Entities;
using MishraReader.Entities.Network;
using MishraReader.Entities.Tools;
using MetroLog;

namespace MishraReader.Services
{
    public class GoogleReaderService : Notifier, IGoogleReaderService
    {
        public enum ServiceStates
        {
            Unloaded,
            Loading,
            Loaded,
        }

        private static readonly ILogger Log = LogManagerFactory.DefaultLogManager.GetLogger<GoogleReaderService>();

        private readonly IErrorService _errorService;
        private readonly INetworkScheduler _networkScheduler;
        private readonly IOptionsManager _optionsManager;
        private readonly AsyncLock _lock = new AsyncLock();
        private IGoogleReaderStream _allCategory;
        private string _continuationAllToken = String.Empty;
        private string _continuationStarredToken = String.Empty;
        private string _continuationUnreadToken = String.Empty;
        private bool _isSync;
        private bool _isSyncAll;
        private bool _isSyncStarred;
        private bool _isSyncUnread;
        private bool _isSyncUnreadCount;
        private DateTime? _lastAllRequest;
        private DateTime? _lastStarredRequest;
        private DateTime? _lastUnreadRequest;
        private readonly IGoogleReaderConnector _readerConnector;
        private ServiceStates _serviceState = ServiceStates.Unloaded;

        private IGoogleReaderStream _unreadCategory;
        private int _unreadCount;

        /// <summary>
        ///   Ctr
        /// </summary>
        public GoogleReaderService(IOptionsManager optionsManager,
                                   INetworkScheduler networkScheduler,
                                   IErrorService errorService,
                                   IGoogleReaderConnector connector)
        {
            _optionsManager = optionsManager;
            _networkScheduler = networkScheduler;
            _errorService = errorService;
            _readerConnector = connector;

            Initialize().Wait();
        }

        public IGoogleReaderConnector Connector
        {
            get { return _readerConnector; }
        }

        /// <summary>
        ///   Gets if service is synchronizing with server
        /// </summary>
        public bool IsSync
        {
            get { return _isSync; }
            set
            {
                Set(ref _isSync, value);
            }
        }


        /// <summary>
        ///   Gets the service state
        /// </summary>
        public ServiceStates ServiceState
        {
            get { return _serviceState; }
            private set
            {
                Set(ref _serviceState, value);
            }
        }


        public SubscriptionsList SubscriptionsList { get; private set; }

        public AsyncLock SyncRoot
        {
            get { return _lock; }
        }

        public int UnreadCount
        {
            get { return _unreadCount; }
            set
            {
                Set(ref _unreadCount, value);
            }
        }

        private bool IsSyncAll
        {
            get { return _isSyncAll; }
            set
            {
                if (_isSyncAll != value)
                {
                    _isSyncAll = value;
                    UpdateIsSync();
                }
            }
        }

        private bool IsSyncStarred
        {
            get { return _isSyncStarred; }
            set
            {
                if (_isSyncStarred != value)
                {
                    _isSyncStarred = value;
                    UpdateIsSync();
                }
            }
        }

        private bool IsSyncUnread
        {
            get { return _isSyncUnread; }
            set
            {
                if (_isSyncUnread != value)
                {
                    _isSyncUnread = value;
                    UpdateIsSync();
                }
            }
        }

        private bool IsSyncUnreadCount
        {
            get { return _isSyncUnreadCount; }
            set
            {
                if (_isSyncUnreadCount != value)
                {
                    _isSyncUnreadCount = value;
                    UpdateIsSync();
                }
            }
        }

        public async Task Disconnect()
        {
            await Initialize();
            Connector.Disconnect();
        }

        /// <summary>
        ///   LoadAsync the service
        /// </summary>
        public async Task LoadAsync()
        {
            if (ServiceState != ServiceStates.Unloaded)
                return;
            ServiceState = ServiceStates.Loading;


            UpdateIsSync();

            try
            {
                SubscriptionsList = await Connector.GetSubscriptionsListAsync();
                ServiceState = ServiceStates.Loaded;
            }
            catch (Exception e)
            {
                ServiceState = ServiceStates.Unloaded;
                _errorService.HandleException(e);
            }


            UpdateUnreadCount();
            UpdateIsSync();

            RaisePropertyChanged(() => SubscriptionsList);
        }

        public async Task<IEnumerable<Item>> LoadAllAsync(bool requestMoreData, IGoogleReaderStream category)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (requestMoreData || _lastAllRequest == null)
                {
                    // Send request
                    _lastAllRequest = DateTime.UtcNow;
                    return await LoadAllItems(false, category);
                }


               return await LoadAllItems(true, category);
            }
        }

        public async Task<IEnumerable<Item>> LoadMoreStarredAsync(bool requestMoreData, IGoogleReaderStream category)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (requestMoreData || _lastStarredRequest == null)
                {
                    _lastStarredRequest = DateTime.UtcNow;
                    return await LoadStarredItems(false);
                }

                return await LoadStarredItems(true);
            }
        }


        public async Task<IEnumerable<Item>> LoadMoreUnreadAsync(bool requestMoreData, IGoogleReaderStream category)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (requestMoreData || _lastUnreadRequest == null)
                {
                    // Send request
                    _lastUnreadRequest = DateTime.UtcNow;

                    return await LoadUnreadItems(false, category);
                }
                else
                {
                    return await LoadUnreadItems(true, category);
                }
            }
        }

  

        public async Task MarkAllAsReadAsync(IGoogleReaderStream category)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (category != null)
                {
                    var subscriptions = category.Subscriptions.Where(s => s.HasUnreads);
                    foreach (var subscription in subscriptions)
                    {
                        subscription.MarkAllAsRead(true);
                    }
                }
                else
                {
                    SubscriptionsList.Subscriptions.ForEach(s => s.MarkAllAsRead(true));
                }

                try
                {
                    await Connector.MarkAllAsReadAsync(category);
                }
                catch (Exception ex)
                {
                    Log.Error("MarkAllAsReadAsync", ex);
                    _errorService.HandleException(ex);
                }
                UpdateUnreadCount();
            }
        }
       
        /// <summary>
        ///   Mark as read. The request is queued to prevent network overhead and unsafe collection access
        /// </summary>
        /// <param name="item"> </param>
        public async Task MarkAsReadAsync(Item item)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (item.CanMarkAsRead())
                {
                    item.MarkAsRead();
                    try
                    {
                        await Connector.MarkItemAsReadAsync(item);

                        UpdateUnreadCount();
                    }
                    catch (Exception e)
                    {
                        Log.Error("MarkAsReadAsync", e);
                        _errorService.HandleException(e);
                    }
                }
            }
        }

        /// <summary>
        ///   Mark as starred. The request is queued to prevent network overhead and unsafe collection access
        /// </summary>
        /// <param name="item"> </param>
        public async Task MarkAsStarredAsync(Item item)
        {
            // To prevent sync issues, mark the item as read first -- GReader is strange with this
            await MarkAsReadAsync(item);
            
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (item.MarkAsStarred())
                {
                    try
                    {
                        await Connector.MarkItemAsStarredAsync(item);
                    }
                    catch (Exception e)
                    {
                        item.UnmarkAsStarred();
                        Log.Error("MarkAsStarredAsync", e);
                        _errorService.HandleException(e);
                    }
                }
            }
        }


        /// <summary>
        ///   Mark as unread. The request is queued to prevent network overhead and unsafe collection access
        /// </summary>
        /// <param name="item"> </param>
        public async Task MarkAsUnreadAsync(Item item)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (item.CanMarkAsUnread())
                {
                    item.MarkAsUnread();

                    UpdateUnreadCount();

                    try
                    {
                        await Connector.MarkItemAsUnreadAsync(item);
                    }
                    catch (Exception e)
                    {
                        Log.Error("MarkAsUnreadAsync", e);
                        _errorService.HandleException(e);
                    }
                }
            }
        }

        /// <summary>
        ///   Mark as unstarred. The request is queued to prevent network overhead and unsafe collection access
        /// </summary>
        /// <param name="item"> </param>
        public async Task MarkAsUnstarredAsync(Item item)
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (item.UnmarkAsStarred())
                {
                    try
                    {
                        await Connector.UnmarkItemAsStarredAsync(item);
                    }
                    catch (Exception e)
                    {
                        item.MarkAsStarred();
                        Log.Error("MarkAsUnstarredAsync", e);
                        _errorService.HandleException(e);
                    }

                    UpdateUnreadCount();
                }
            }
        }

        /// <summary>
        /// </summary>
        public async Task<Tuple<bool, int>> RefreshUnreadAsync()
        {
            using (var releaser = await SubscriptionsList.LockAsync())
            {
                if (IsSyncUnreadCount)
                    return null;

                IsSyncUnreadCount = true;

                try
                {
                    var result = await _networkScheduler.Factory.StartNew(
                        async () => await Connector.UpdateUnreadCountAsync(SubscriptionsList)).Unwrap();

                    UpdateUnreadCount();

                    return result;
                }
                catch (Exception e)
                {
                    Log.Error("RefreshUnreadAsync", e);
                    _errorService.HandleException(e);
                }
                finally
                {
                    IsSyncUnreadCount = false;
                }

                return null;
            }
        }


        public void Reset()
        {
            _lastAllRequest = null;
            _lastStarredRequest = null;
            _lastUnreadRequest = null;
            _continuationStarredToken = String.Empty;
            _continuationUnreadToken = String.Empty;
            _continuationAllToken = String.Empty;
        }


        public void UpdateUnreadCount()
        {
            UnreadCount = SubscriptionsList.UnreadsCount;
        }

        private async Task Initialize()
        {
            using (var releaser = await _lock.LockAsync())
            {
                Reset();
                ServiceState = ServiceStates.Unloaded;
                SubscriptionsList = null;
                _unreadCategory = null;
                _allCategory = null;
                _isSyncUnread = false;
                _isSyncStarred = false;
                _isSyncAll = false;
                IsSync = false;
                UnreadCount = 0;
            }
        }

        private IEnumerable<Item> AsSortedItems(IEnumerable<Subscription> subscription, Func<Subscription, IEnumerable<Item>> selector)
        {
            if (_optionsManager.OrderByPublicationDateDesc)
                return from sub in subscription
                       from item in selector(sub)
                       orderby item.PublicationDate descending
                       select item;

            return from sub in subscription
                   from item in selector(sub)
                   orderby item.PublicationDate
                   select item;
        }

        private async Task<IEnumerable<Item>> LoadAllItems(bool useLocalData, IGoogleReaderStream category)
        {
            // If continuationToken is null, there's no more data
            if (useLocalData == false && _continuationAllToken == null)
            {
                return null;
            }

            if (!Equals(_allCategory, category))
            {
                _allCategory = category;
            }
            else
            {
                if (IsSyncAll)
                {
                    return null;
                }
            }

            IsSyncAll = true;
            
            try
            {
                var result = await _networkScheduler.Factory.StartNew(
                    async () =>
                              {
                                  if (useLocalData)
                                  {
                                      var subs = category != null
                                                     ? category.Subscriptions
                                                     : SubscriptionsList.Subscriptions;

                                      return AsSortedItems(subs, s => s.Items).ToList();
                                  }

                                  var list = await Connector.LoadAllItemsAsync(SubscriptionsList, category,
                                                                               _continuationAllToken,
                                                                               _optionsManager.
                                                                                   MaxItemsByRequest);
                                  if (list != null)
                                  {
                                      _continuationAllToken = list.ContinuationToken;

                                      return list.Items;
                                  }

                                  return null;
                              }).Unwrap();

                //if (_allCategory != category)
                //    return Enumerable.Empty<Item>();

                return result;
            }
            catch(Exception e)
            {
                Log.Error("LoadAllItems", e);
                _errorService.HandleException(e);
            }
            finally
            {
                IsSyncAll = false;
            }

            return null;
        }

        private async Task<IEnumerable<Item>> LoadStarredItems(bool useLocalData)
        {
            // If continuationToken is null, there's no more data
            if (useLocalData == false && _continuationStarredToken == null)
                return null;

            if (IsSyncStarred)
                return null;

            IsSyncStarred = true;

            try
            {
                var result = await _networkScheduler.Factory.StartNew(
                    async () =>
                              {
                                  if (useLocalData)
                                  {
                                      return AsSortedItems(SubscriptionsList.Subscriptions, s => s.StarredItems).ToList();
                                  }

                                  var list = await Connector.LoadStarredItemsAsync(SubscriptionsList,
                                                                                   _continuationStarredToken,
                                                                                   _optionsManager.
                                                                                       MaxItemsByRequest);
                                  if (list != null)
                                  {
                                      _continuationStarredToken = list.ContinuationToken;

                                      return list.Items;
                                  }

                                  return null;
                              }).Unwrap();

                return result;
            }
            catch(Exception e)
            {
                Log.Error("LoadStarredItems", e);
                _errorService.HandleException(e);
            }
            finally
            {
                IsSyncStarred = false;    
            }

            return null;
        }

        private async Task<IEnumerable<Item>> LoadUnreadItems(bool useLocalData, IGoogleReaderStream category)
        {
            // If continuationToken is null, there's no more data
            if (useLocalData == false && _continuationUnreadToken == null)
            {
                //Debug.WriteLine("No more data " + catLab);
                return null;
            }

            if (_unreadCategory != category)
            {
                _unreadCategory = category;
            }
            else
            {
                if (IsSyncUnread)
                {
                    //Debug.WriteLine("Already loading " + catLab);
                    return null;
                }
            }

            IsSyncUnread = true;

            IEnumerable<Item> result = null;
            try
            {
                 result = await _networkScheduler.Factory.StartNew(
                    async () =>
                              {
                                  if (useLocalData)
                                  {
                                      var subs = category != null
                                                     ? category.Subscriptions
                                                     : SubscriptionsList.Subscriptions;

                                      return AsSortedItems(subs, s => s.UnreadItems.Cast<Item>()).ToList();
                                  }

                                  var connector = Connector;

                                  var list = await connector.LoadUnreadItemsAsync(SubscriptionsList, category,
                                                                                  _continuationUnreadToken,
                                                                                  _optionsManager.
                                                                                      MaxItemsByRequest);
                                  if (list != null)
                                  {
                                      _continuationUnreadToken = list.ContinuationToken;

                                      return list.Items;
                                  }

                                  return null;
                              }).Unwrap();
            }
            catch(Exception e)
            {
                Log.Error("LoadUnreadItems", e);
                _errorService.HandleException(e);
            }
            finally
            {
                IsSyncUnread = false;
            }

            if (_unreadCategory != category)
            {
                return null;
            }

            

            return result;
        }

  

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(UnreadCount >= 0);
        }

        private void UpdateIsSync()
        {
            IsSync =
                IsSyncUnreadCount ||
                IsSyncUnread ||
                IsSyncStarred ||
                IsSyncAll ||
                ServiceState == ServiceStates.Loading;
        }
    }
}